/*-------------------------------------------------------------------------
 *
 * execAmi.c
 *	  杂项执行器访问方法例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *	src/backend/executor/execAmi.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/amapi.h"
#include "access/htup_details.h"
#include "executor/execdebug.h"
#include "executor/nodeAgg.h"
#include "executor/nodeAppend.h"
#include "executor/nodeBitmapAnd.h"
#include "executor/nodeBitmapHeapscan.h"
#include "executor/nodeBitmapIndexscan.h"
#include "executor/nodeBitmapOr.h"
#include "executor/nodeCtescan.h"
#include "executor/nodeCustom.h"
#include "executor/nodeForeignscan.h"
#include "executor/nodeFunctionscan.h"
#include "executor/nodeGather.h"
#include "executor/nodeGatherMerge.h"
#include "executor/nodeGroup.h"
#include "executor/nodeHash.h"
#include "executor/nodeHashjoin.h"
#include "executor/nodeIncrementalSort.h"
#include "executor/nodeIndexonlyscan.h"
#include "executor/nodeIndexscan.h"
#include "executor/nodeLimit.h"
#include "executor/nodeLockRows.h"
#include "executor/nodeMaterial.h"
#include "executor/nodeMemoize.h"
#include "executor/nodeMergeAppend.h"
#include "executor/nodeMergejoin.h"
#include "executor/nodeModifyTable.h"
#include "executor/nodeNamedtuplestorescan.h"
#include "executor/nodeNestloop.h"
#include "executor/nodeProjectSet.h"
#include "executor/nodeRecursiveunion.h"
#include "executor/nodeResult.h"
#include "executor/nodeSamplescan.h"
#include "executor/nodeSeqscan.h"
#include "executor/nodeSetOp.h"
#include "executor/nodeSort.h"
#include "executor/nodeSubplan.h"
#include "executor/nodeSubqueryscan.h"
#include "executor/nodeTableFuncscan.h"
#include "executor/nodeTidrangescan.h"
#include "executor/nodeTidscan.h"
#include "executor/nodeUnique.h"
#include "executor/nodeValuesscan.h"
#include "executor/nodeWindowAgg.h"
#include "executor/nodeWorktablescan.h"
#include "nodes/extensible.h"
#include "nodes/nodeFuncs.h"
#include "nodes/pathnodes.h"
#include "utils/rel.h"
#include "utils/syscache.h"

static bool fc_IndexSupportsBackwardScan(Oid fc_indexid);


/*
 * ExecReScan
 *		重置一个计划节点，以便其输出可以重新扫描。
 *
 * 注意，如果计划节点的参数发生了值的变化，
 * 输出可能会与上次不同。
 */
void ExecReScan(PlanState *fc_node)
{
	/* 如果正在收集时间统计信息，请更新它们 */
	if (fc_node->instrument)
		InstrEndLoop(fc_node->instrument);

	/*
	 * 如果我们已经更改了参数，请传播该信息。
	 *
	 * 注意：ExecReScanSetParamPlan() 可以将位添加到 node->chgParam，
	 * 对应于 InitPlan 将更新的输出参数。
	 * 由于我们仅遍历一次列表，这意味着 InitPlan
	 * 只能依赖于列表中早于它的兄弟 InitPlan 的输出参数。
	 * 鉴于一个 InitPlan 依赖于另一个的方式非常有限，
	 * 目前这是可行的，但最终我们可能需要更努力地工作（或者让计划器
	 * 扩大 extParam/allParam 集合，以包括依赖的 InitPlans 的参数）。
	 */
	if (fc_node->chgParam != NULL)
	{
		ListCell   *fc_l;

		foreach(fc_l, fc_node->initPlan)
		{
			SubPlanState *fc_sstate = (SubPlanState *) lfirst(fc_l);
			PlanState  *fc_splan = fc_sstate->planstate;

			if (fc_splan->plan->extParam != NULL)	/* 不关心子级
 * 的局部参数 */
				UpdateChangedParamSet(fc_splan, fc_node->chgParam);
			if (fc_splan->chgParam != NULL)
				ExecReScanSetParamPlan(fc_sstate, fc_node);
		}
		foreach(fc_l, fc_node->subPlan)
		{
			SubPlanState *fc_sstate = (SubPlanState *) lfirst(fc_l);
			PlanState  *fc_splan = fc_sstate->planstate;

			if (fc_splan->plan->extParam != NULL)
				UpdateChangedParamSet(fc_splan, fc_node->chgParam);
		}
		/* 好吧。现在为左右树设置 chgParam。 */
		if (fc_node->lefttree != NULL)
			UpdateChangedParamSet(fc_node->lefttree, fc_node->chgParam);
		if (fc_node->righttree != NULL)
			UpdateChangedParamSet(fc_node->righttree, fc_node->chgParam);
	}

	/* 调用表达式回调 */
	if (fc_node->ps_ExprContext)
		ReScanExprContext(fc_node->ps_ExprContext);

	/* 并进行节点类型特定的处理 */
	switch (nodeTag(fc_node))
	{
		case T_ResultState:
			ExecReScanResult((ResultState *) fc_node);
			break;

		case T_ProjectSetState:
			ExecReScanProjectSet((ProjectSetState *) fc_node);
			break;

		case T_ModifyTableState:
			ExecReScanModifyTable((ModifyTableState *) fc_node);
			break;

		case T_AppendState:
			ExecReScanAppend((AppendState *) fc_node);
			break;

		case T_MergeAppendState:
			ExecReScanMergeAppend((MergeAppendState *) fc_node);
			break;

		case T_RecursiveUnionState:
			ExecReScanRecursiveUnion((RecursiveUnionState *) fc_node);
			break;

		case T_BitmapAndState:
			ExecReScanBitmapAnd((BitmapAndState *) fc_node);
			break;

		case T_BitmapOrState:
			ExecReScanBitmapOr((BitmapOrState *) fc_node);
			break;

		case T_SeqScanState:
			ExecReScanSeqScan((SeqScanState *) fc_node);
			break;

		case T_SampleScanState:
			ExecReScanSampleScan((SampleScanState *) fc_node);
			break;

		case T_GatherState:
			ExecReScanGather((GatherState *) fc_node);
			break;

		case T_GatherMergeState:
			ExecReScanGatherMerge((GatherMergeState *) fc_node);
			break;

		case T_IndexScanState:
			ExecReScanIndexScan((IndexScanState *) fc_node);
			break;

		case T_IndexOnlyScanState:
			ExecReScanIndexOnlyScan((IndexOnlyScanState *) fc_node);
			break;

		case T_BitmapIndexScanState:
			ExecReScanBitmapIndexScan((BitmapIndexScanState *) fc_node);
			break;

		case T_BitmapHeapScanState:
			ExecReScanBitmapHeapScan((BitmapHeapScanState *) fc_node);
			break;

		case T_TidScanState:
			ExecReScanTidScan((TidScanState *) fc_node);
			break;

		case T_TidRangeScanState:
			ExecReScanTidRangeScan((TidRangeScanState *) fc_node);
			break;

		case T_SubqueryScanState:
			ExecReScanSubqueryScan((SubqueryScanState *) fc_node);
			break;

		case T_FunctionScanState:
			ExecReScanFunctionScan((FunctionScanState *) fc_node);
			break;

		case T_TableFuncScanState:
			ExecReScanTableFuncScan((TableFuncScanState *) fc_node);
			break;

		case T_ValuesScanState:
			ExecReScanValuesScan((ValuesScanState *) fc_node);
			break;

		case T_CteScanState:
			ExecReScanCteScan((CteScanState *) fc_node);
			break;

		case T_NamedTuplestoreScanState:
			ExecReScanNamedTuplestoreScan((NamedTuplestoreScanState *) fc_node);
			break;

		case T_WorkTableScanState:
			ExecReScanWorkTableScan((WorkTableScanState *) fc_node);
			break;

		case T_ForeignScanState:
			ExecReScanForeignScan((ForeignScanState *) fc_node);
			break;

		case T_CustomScanState:
			ExecReScanCustomScan((CustomScanState *) fc_node);
			break;

		case T_NestLoopState:
			ExecReScanNestLoop((NestLoopState *) fc_node);
			break;

		case T_MergeJoinState:
			ExecReScanMergeJoin((MergeJoinState *) fc_node);
			break;

		case T_HashJoinState:
			ExecReScanHashJoin((HashJoinState *) fc_node);
			break;

		case T_MaterialState:
			ExecReScanMaterial((MaterialState *) fc_node);
			break;

		case T_MemoizeState:
			ExecReScanMemoize((MemoizeState *) fc_node);
			break;

		case T_SortState:
			ExecReScanSort((SortState *) fc_node);
			break;

		case T_IncrementalSortState:
			ExecReScanIncrementalSort((IncrementalSortState *) fc_node);
			break;

		case T_GroupState:
			ExecReScanGroup((GroupState *) fc_node);
			break;

		case T_AggState:
			ExecReScanAgg((AggState *) fc_node);
			break;

		case T_WindowAggState:
			ExecReScanWindowAgg((WindowAggState *) fc_node);
			break;

		case T_UniqueState:
			ExecReScanUnique((UniqueState *) fc_node);
			break;

		case T_HashState:
			ExecReScanHash((HashState *) fc_node);
			break;

		case T_SetOpState:
			ExecReScanSetOp((SetOpState *) fc_node);
			break;

		case T_LockRowsState:
			ExecReScanLockRows((LockRowsState *) fc_node);
			break;

		case T_LimitState:
			ExecReScanLimit((LimitState *) fc_node);
			break;

		default:
			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(fc_node));
			break;
	}

	if (fc_node->chgParam != NULL)
	{
		bms_free(fc_node->chgParam);
		fc_node->chgParam = NULL;
	}
}

/*
 * ExecMarkPos
 *
 * 标记当前扫描位置。
 *
 * 注意：目前仅仅需要在 MergeJoin 节点的直接内子节点的计划节点中
 * 支持标记/恢复功能。由于 MergeJoin 需要排序输入，因此在
 * 不能产生排序输出的节点类型中，从不需要支持标记/恢复。
 * 在一些情况下，节点可以从其子级传递排序数据；如果我们不
 * 为这样的节点类型实现标记/恢复，计划器通过在该节点上方插入
 * Material 节点进行补偿。
 */
void ExecMarkPos(PlanState *fc_node)
{
	switch (nodeTag(fc_node))
	{
		case T_IndexScanState:
			ExecIndexMarkPos((IndexScanState *) fc_node);
			break;

		case T_IndexOnlyScanState:
			ExecIndexOnlyMarkPos((IndexOnlyScanState *) fc_node);
			break;

		case T_CustomScanState:
			ExecCustomMarkPos((CustomScanState *) fc_node);
			break;

		case T_MaterialState:
			ExecMaterialMarkPos((MaterialState *) fc_node);
			break;

		case T_SortState:
			ExecSortMarkPos((SortState *) fc_node);
			break;

		case T_ResultState:
			ExecResultMarkPos((ResultState *) fc_node);
			break;

		default:
			/* 除非调用者请求恢复，否则不产生致命错误... */
			elog(DEBUG2, "unrecognized node type: %d", (int) nodeTag(fc_node));
			break;
	}
}

/*
 * ExecRestrPos
 *
 * 恢复先前通过 ExecMarkPos() 保存的扫描位置。
 *
 * 注意：其语义是，第一个 ExecProcNode 在
 * 恢复操作之后将产生与标记操作之后第一个相同的元组。
 * 对于计划节点的结果 TupleTableSlot 来说，其后果是未指定的。
 * （在大多数情况下，结果槽在恢复时保持不变，但节点可能选择清除
 * 或加载恢复到的元组。）因此，调用者在执行恢复后应丢弃任何
 * 先前返回的 TupleTableSlot。
 */
void ExecRestrPos(PlanState *fc_node)
{
	switch (nodeTag(fc_node))
	{
		case T_IndexScanState:
			ExecIndexRestrPos((IndexScanState *) fc_node);
			break;

		case T_IndexOnlyScanState:
			ExecIndexOnlyRestrPos((IndexOnlyScanState *) fc_node);
			break;

		case T_CustomScanState:
			ExecCustomRestrPos((CustomScanState *) fc_node);
			break;

		case T_MaterialState:
			ExecMaterialRestrPos((MaterialState *) fc_node);
			break;

		case T_SortState:
			ExecSortRestrPos((SortState *) fc_node);
			break;

		case T_ResultState:
			ExecResultRestrPos((ResultState *) fc_node);
			break;

		default:
			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(fc_node));
			break;
	}
}

/*
 * ExecSupportsMarkRestore - 路径是否支持标记/恢复？
 *
 * 这在规划阶段使用，因此必须接受路径，而不是计划。
 * 我们将其放在这里，以便与以上的例程相邻，这些例程也必须
 * 知道哪些计划类型支持标记/恢复。
 */
bool ExecSupportsMarkRestore(Path *fc_pathnode)
{
	/*
	 * 为了与上述例程的一致性，我们不检查 nodeTag
	 * 而是检查 pathtype，即路径将产生的计划节点类型。
	 */
	switch (fc_pathnode->pathtype)
	{
		case T_IndexScan:
		case T_IndexOnlyScan:

			/*
			 * 不是所有索引类型都支持标记/恢复。
			 */
			return castNode(IndexPath, fc_pathnode)->indexinfo->amcanmarkpos;

		case T_Material:
		case T_Sort:
			return true;

		case T_CustomScan:
			if (castNode(CustomPath, fc_pathnode)->flags & CUSTOMPATH_SUPPORT_MARK_RESTORE)
				return true;
			return false;

		case T_Result:

			/*
			 * 结果仅在其子计划支持标记/恢复时才支持标记/恢复。
			 *
			 * 我们在这里必须小心，因为有不止一种路径类型
			 * 可以产生一个结果计划节点。
			 */
			if (IsA(fc_pathnode, ProjectionPath))
				return ExecSupportsMarkRestore(((ProjectionPath *) fc_pathnode)->subpath);
			else if (IsA(fc_pathnode, MinMaxAggPath))
				return false;	/* 没有子级的结果 */
			else if (IsA(fc_pathnode, GroupResultPath))
				return false;	/* 没有子级的结果 */
			else
			{
				/* 简单的 RTE_RESULT 基本关系 */
				Assert(IsA(fc_pathnode, Path));
				return false;	/* 没有子级的结果 */
			}

		case T_Append:
			{
				AppendPath *fc_appendPath = castNode(AppendPath, fc_pathnode);

				/*
				 * 如果恰好有一个子级，那么最终计划中将没有 Append，
				 * 因此如果子计划节点可以处理，我们可以处理标记/恢复。
				 */
				if (list_length(fc_appendPath->subpaths) == 1)
					return ExecSupportsMarkRestore((Path *) linitial(fc_appendPath->subpaths));
				/* 否则，Append 无法处理 */
				return false;
			}

		case T_MergeAppend:
			{
				MergeAppendPath *fc_mapath = castNode(MergeAppendPath, fc_pathnode);

				/*
				 * 与上面的 Append 案例类似，单子路径的 MergeAppends
				 * 不会出现在最终计划中，因此只需返回子计划的
				 * 标记/恢复能力。
				 */
				if (list_length(fc_mapath->subpaths) == 1)
					return ExecSupportsMarkRestore((Path *) linitial(fc_mapath->subpaths));
				/* 否则，MergeAppend 无法处理 */
				return false;
			}

		default:
			break;
	}

	return false;
}

/*
 * ExecSupportsBackwardScan - 计划类型是否支持向后扫描？
 *
 * 理想情况下，所有计划类型都应该支持向后扫描，但这似乎
 * 不太可能很快发生。在某些情况下，计划节点将向后扫描
 * 传递给其子级，因此仅在其子级支持的情况下，才支持向后扫描。
 * 因此，该例程必须传递一个完整的计划树。
 */
bool ExecSupportsBackwardScan(Plan *fc_node)
{
	if (fc_node == NULL)
		return false;

	/*
	 * 并行感知节点在每个工作器中返回元组的一个子集，通常我们不能期望有足够的登记状态来知道我们在这个工作器中返回了哪些元组，而不是在其他工作器中。
	 */
	if (fc_node->parallel_aware)
		return false;

	switch (nodeTag(fc_node))
	{
		case T_Result:
			if (outerPlan(fc_node) != NULL)
				return ExecSupportsBackwardScan(outerPlan(fc_node));
			else
				return false;

		case T_Append:
			{
				ListCell   *fc_l;

				/* 使用异步时，元组可能会交错，因此不能回退。 */
				if (((Append *) fc_node)->nasyncplans > 0)
					return false;

				foreach(fc_l, ((Append *) fc_node)->appendplans)
				{
					if (!ExecSupportsBackwardScan((Plan *) lfirst(fc_l)))
						return false;
				}
				/* 不需要检查 tlist，因为 Append 不会评估它 */
				return true;
			}

		case T_SampleScan:
			/* 通过不允许这个来简化表抽样方法的生活 */
			return false;

		case T_Gather:
			return false;

		case T_IndexScan:
			return fc_IndexSupportsBackwardScan(((IndexScan *) fc_node)->indexid);

		case T_IndexOnlyScan:
			return fc_IndexSupportsBackwardScan(((IndexOnlyScan *) fc_node)->indexid);

		case T_SubqueryScan:
			return ExecSupportsBackwardScan(((SubqueryScan *) fc_node)->subplan);

		case T_CustomScan:
			if (((CustomScan *) fc_node)->flags & CUSTOMPATH_SUPPORT_BACKWARD_SCAN)
				return true;
			return false;

		case T_SeqScan:
		case T_TidScan:
		case T_TidRangeScan:
		case T_FunctionScan:
		case T_ValuesScan:
		case T_CteScan:
		case T_Material:
		case T_Sort:
			/* 这些不会评估 tlist */
			return true;

		case T_IncrementalSort:

			/*
			 * 与完全排序不同，增量排序仅在内存中保留一组元组，因此无法向后扫描。
			 */
			return false;

		case T_LockRows:
		case T_Limit:
			return ExecSupportsBackwardScan(outerPlan(fc_node));

		default:
			return false;
	}
}

/*
 * IndexScan 或 IndexOnlyScan 节点仅在索引的 AM 支持的情况下才支持向后扫描。
 */
static bool fc_IndexSupportsBackwardScan(Oid fc_indexid)
{
	bool		fc_result;
	HeapTuple	fc_ht_idxrel;
	Form_pg_class fc_idxrelrec;
	IndexAmRoutine *fc_amroutine;

	/* 获取索引关系的 pg_class 元组 */
	fc_ht_idxrel = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_indexid));
	if (!HeapTupleIsValid(fc_ht_idxrel))
		elog(ERROR, "cache lookup failed for relation %u", fc_indexid);
	fc_idxrelrec = (Form_pg_class) GETSTRUCT(fc_ht_idxrel);

	/* 获取索引 AM 的 API 结构 */
	fc_amroutine = GetIndexAmRoutineByAmId(fc_idxrelrec->relam, false);

	fc_result = fc_amroutine->amcanbackward;

	pfree(fc_amroutine);
	ReleaseSysCache(fc_ht_idxrel);

	return fc_result;
}

/*
 * ExecMaterializesOutput - 一个计划类型是否物化其输出？
 *
 * 如果计划节点类型是自动物化其输出的类型（通常通过将其保存在元组存储中），则返回 true。对于这样的计划，重新扫描而没有任何参数变化将具有零启动成本和非常低的每元组成本。
 */
bool ExecMaterializesOutput(NodeTag fc_plantype)
{
	switch (fc_plantype)
	{
		case T_Material:
		case T_FunctionScan:
		case T_TableFuncScan:
		case T_CteScan:
		case T_NamedTuplestoreScan:
		case T_WorkTableScan:
		case T_Sort:
			return true;

		default:
			break;
	}

	return false;
}
